1 import tkinter
2 from tkinter import ttk
3 import subprocess
4 import json
5 from functools import partial
6 import tkinter.messagebox
7 import socket
8 import os
9 """
10 Add close button to reports
11 pip3 download support
12 """
13 scriptdir = os.path.dirname(os.path.abspath(__file__))+"/"
14 merrygui = None
15 fmod = {}
16
17 class CreateToolTip(object):
18 """
19 create a tooltip for a given widget
20 """
21 def __init__(self, widget, text='widget info'):
22 self.waittime = 500 #miliseconds
23 self.wraplength = 180 #pixels
24 self.widget = widget
25 self.text = text
26 self.widget.bind("<Enter>", self.enter)
27 self.widget.bind("<Leave>", self.leave)
28 self.widget.bind("<ButtonPress>", self.leave)
29 self.id = None
30 self.tw = None
31
32 def enter(self, event=None):
33 self.schedule()
34
35 def leave(self, event=None):
36 self.unschedule()
37 self.hidetip()
38
39 def schedule(self):
40 self.unschedule()
41 self.id = self.widget.after(self.waittime, self.showtip)
42
43 def unschedule(self):
44 id = self.id
45 self.id = None
46 if id:
47 self.widget.after_cancel(id)
48
49 def showtip(self, event=None):
50 x = y = 0
51 x, y, cx, cy = self.widget.bbox("insert")
52 x += self.widget.winfo_rootx() + 25
53 y += self.widget.winfo_rooty() + 20
54 # creates a toplevel window
55 self.tw = tkinter.Toplevel(self.widget)
56 # Leaves only the label and removes the app window
57 self.tw.wm_overrideredirect(True)
58 self.tw.wm_geometry("+%d+%d" % (x, y))
59 label = tkinter.Label(self.tw, text=self.text, justify='left',
60 background="#ffffff", relief='solid', borderwidth=1,
61 wraplength = self.wraplength)
62 label.pack(ipadx=1)
63
64 def hidetip(self):
65 tw = self.tw
66 self.tw= None
67 if tw:
68 tw.destroy()
69
70 def __str__(self):
71 return "CreateToolTip"
72
73 def internet(host="8.8.8.8", port=53, timeout=3):
74 try:
75 socket.setdefaulttimeout(timeout)
76 socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
77 return True
78 except Exception as ex:
79 print( ex)
80 return False
81
82 def build_package_dict(output):
83 global fmod
84 lines = output.split("\n")
85 modules_outdated = lines[2:]
86 fmod = {}
87 i = 0
88 for item in modules_outdated:
89 f = item.split(" ")
90 m = []
91 i += 1
92 for fi in f:
93 if fi:
94 m.append(fi)
95 if len(m) > 0:
96 fmod[i] = m
97 return fmod
98
99 def get_modules(host):
100 debug = False
101 if debug:
102 output = """Package Version
103 ---------- ---------
104 certifi 2018.4.16
105 psutil 5.4.6
106 pycairo 1.17.0
107 PyQt5-sip 4.19.11
108 setuptools 39.2.0
109 """
110 else:
111 res = subprocess.run([host.pip, "list"], stdout=subprocess.PIPE)
112 output = str(res.stdout,"latin-1")
113 data = build_package_dict(output)
114 host.modules.delete(0, tkinter.END)
115 for item in data:
116 host.modules.insert(tkinter.END, data[item][0])
117 print(data)
118 host.b_update.config(state="disabled")
119 host.b_uninstall.config(state="normal")
120
121 def get_updates(host):
122 debug = False
123 if debug:
124 output = """Package Version Latest Type
125 ---------- --------- --------- -----
126 certifi 2018.4.16 2018.8.24 wheel
127 psutil 5.4.6 5.4.7 sdist
128 pycairo 1.17.0 1.17.1 sdist
129 PyQt5-sip 4.19.11 4.19.12 wheel
130 setuptools 39.2.0 40.2.0 wheel
131 """
132 else:
133 res = subprocess.run([host.pip, "list", "--outdated"], stdout=subprocess.PIPE)
134 output = str(res.stdout,"latin-1")
135 data = build_package_dict(output)
136 host.modules.delete(0, tkinter.END)
137 if len(data) > 0:
138 for item in data:
139 host.modules.insert(tkinter.END, data[item][0])
140 print(data)
141 host.b_update.config(state="normal")
142 host.b_uninstall.config(state="normal")
143 tkinter.messagebox.showinfo(title="Result", message=f"{len(data)} updates found!")
144 else:
145 merrygui.infolab.config(text="No updates found!")
146 tkinter.messagebox.showinfo(title="Result", message=f"No updates found!")
147
148 def getConfig():
149 with open("config.json") as f:
150 return json.load(f)
151
152 def setConfig(key:str, value):
153 data = getConfig()
154 data[key] = value
155 with open('config.json', "w") as s:
156 json.dump(data, s, indent=4, sort_keys=True)
157
158 def dumpConfig(data):
159 with open('config.json', "w") as s:
160 json.dump(data, s, indent=4, sort_keys=True)
161
162 def boolinate(string):
163 try:
164 truth = ['true', '1', 'yes', 'on']
165 if string.lower() in truth:
166 return True
167 else:
168 return False
169 except:
170 return string
171
172 def install_module(module):
173 print("will install "+module.get())
174
175 if merrygui.usermode:
176 res = subprocess.run([merrygui.pip, "install", "--user", module.get()], stdout=subprocess.PIPE)
177 else:
178 res = subprocess.run([merrygui.pip, "install", module.get()], stdout=subprocess.PIPE)
179 output = str(res.stdout,"latin-1")
180 #tkinter.messagebox.showinfo(title="Result", message=output)
181 r = tkinter.Tk()
182 lb = tkinter.Label(r, text=output, justify="left")
183 lb.grid()
184 r.title("Result")
185 r.mainloop()
186
187 def search_module(module):
188 print("will search "+module.get())
189
190 res = subprocess.run([merrygui.pip, "search", module.get()], stdout=subprocess.PIPE)
191 output = str(res.stdout,"latin-1")
192 #tkinter.messagebox.showinfo(title="Result", message=output)
193 r = tkinter.Tk()
194 lb = tkinter.Label(r, text=output, justify="left")
195 lb.grid()
196 r.title("Result")
197 r.mainloop()
198
199
200 def install():
201 w = tkinter.Tk()
202 en = tkinter.Entry(w)
203 run_inst = partial(install_module, en)
204 run_srch = partial(search_module, en)
205 b = tkinter.Button(w, text="Install", command=run_inst, cursor="hand1")
206 sr = tkinter.Button(w, text="Search", command=run_srch, cursor="hand1")
207 en.grid(columnspan=2)
208 b.grid(row=1)
209 sr.grid(row=1, column=1)
210 w.title("Installer")
211 w.mainloop()
212
213 def uninstall():
214 mod = merrygui.modules.curselection()[0]
215 mod += 1
216 if tkinter.messagebox.askokcancel(title=f"Uninstall {fmod[mod][0]}", message=f"{fmod[mod][0]} {fmod[mod][1]} will be COMPLETELY uninstalled."):
217 res = subprocess.run([merrygui.pip, "uninstall", "-y", fmod[mod][0]], stdout=subprocess.PIPE)
218 output = str(res.stdout,"latin-1")
219 merrygui.modules.delete(mod-1)
220 r = tkinter.Tk()
221 lb = tkinter.Label(r, text=output)
222 lb.grid()
223 r.title("Result")
224 r.mainloop()
225
226 def update():
227 mod = merrygui.modules.curselection()[0]
228 mod += 1
229 print(fmod[mod][0])
230 if tkinter.messagebox.askokcancel(title=f"Update {fmod[mod][0]}", message=f"{fmod[mod][0]} will be updated from {fmod[mod][1]} to {fmod[mod][2]}"):
231 if merrygui.usermode:
232 res = subprocess.run([merrygui.pip, "install", "--upgrade", fmod[mod][0]], stdout=subprocess.PIPE)
233 else:
234 res = subprocess.run([merrygui.pip, "install", "--upgrade", "--user", fmod[mod][0]], stdout=subprocess.PIPE)
235 output = str(res.stdout,"latin-1")
236 merrygui.modules.delete(mod-1)
237 r = tkinter.Tk()
238 lb = tkinter.Label(r, text=output, justify="left")
239 lb.grid()
240 r.title("Result")
241 r.mainloop
242
243 def onselect(evt):
244 w = evt.widget
245 try:
246 index = int(w.curselection()[0])
247 except IndexError:
248 return
249 index += 1
250 # value = w.get(index)
251 try:
252 merrygui.infolab.config(text=fmod[index][0]+" - Current Version: "+fmod[index][1]+" - PIP Version: "+fmod[index][2])
253 except:
254 merrygui.infolab.config(text=fmod[index][0]+" "+fmod[index][1])
255
256 def reconnect():
257 if internet():
258 merrygui.b_updatecheck.config(state="normal")
259 merrygui.b_install.config(state="normal")
260 merrygui.b_rec.destroy()
261 merrygui.online = True
262 merrygui.infolab.config(text="Reconnected to network!")
263 else:
264 tkinter.messagebox.showerror(title="No network connection", message="No internet connection was found.\nMerry will run in offline mode. (No update checking.)")
265
266 def pipcheck():
267 res = subprocess.run([merrygui.pip, "check"], stdout=subprocess.PIPE)
268 output = str(res.stdout,"latin-1")
269 r = tkinter.Tk()
270 lb = tkinter.Label(r, text=output)
271 lb.grid()
272 r.title("Result")
273 r.mainloop()
274
275 def pipshow():
276 try:
277 mod = merrygui.modules.curselection()[0]
278 except IndexError:
279 tkinter.messagebox.showerror(title="Error", message="No package selected.")
280 return
281 mod += 1
282 res = subprocess.run([merrygui.pip, "show", fmod[mod][0]], stdout=subprocess.PIPE)
283 output = str(res.stdout,"latin-1")
284 r = tkinter.Tk()
285 lb = tkinter.Label(r, text=output, justify="left")
286 lb.grid()
287 r.title("Result")
288 r.mainloop()
289
290 def about():
291 tkinter.messagebox.showinfo(title="About Merry", message="Merry is a pip GUI interface written by Kaiser.\nSource is available at https://github.com/Kaiz0r/Merry")
292
293 class pipGuiMan:
294 def __init__(self):
295 self.online = internet()
296 self.config = getConfig()
297 self.pip = self.config['pip_command']
298 self.update_check_on_start = boolinate(self.config['auto_update_check'])
299 self.usermode = boolinate(self.config['add_user_flag'])
300 self.mainwin = tkinter.Tk()
301 self.modules = tkinter.Listbox(self.mainwin, height=15)
302
303 self.modules.grid(rowspan=6, columnspan=4)
304
305 self.modules.bind('<<ListboxSelect>>', onselect)
306 ub = partial(get_updates, self)
307 ubi = partial(get_modules, self)
308 self.infolab = tkinter.Label(self.mainwin, text="Selected info will appear here.")
309 self.infolab.grid(row=6, columnspan=6)
310 self.chicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'py.png'))
311 self.listicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'list.png'))
312 self.dlicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'dl.png'))
313 self.unicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'uni.png'))
314 self.upicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'upg.png'))
315 self.b_updatecheck = tkinter.Button(self.mainwin, image=self.chicon, compound="left", text="Check for updates", command=ub, cursor="hand1", width=150, anchor="w")
316 self.b_listall = tkinter.Button(self.mainwin, text="Show list", image=self.listicon, compound="left", command=ubi, cursor="hand1", width=150, anchor="w")
317 self.b_install = tkinter.Button(self.mainwin, image=self.dlicon, compound="left", text="Install...", command=install, cursor="hand1", width=150, anchor="w")
318 self.b_uninstall = tkinter.Button(self.mainwin, image=self.unicon, compound="left", text="Uninstall", command=uninstall, state="disabled", cursor="hand1", width=150, anchor="w")
319 self.b_update = tkinter.Button(self.mainwin, image=self.upicon, compound="left", text="Update", command=update, state="disabled", cursor="hand1", width=150, anchor="w")
320
321 self.b_updatecheck.grid(column=4, row=0)
322 CreateToolTip(self.b_updatecheck, "Gets outdated modules list.\nNOTE: Will take a few moments.")
323 self.b_listall.grid(column=4, row=1)
324 CreateToolTip(self.b_listall, "Gets installed modules list.\nNOTE: Will take a few moments.")
325 self.b_install.grid(column=4, row=2)
326 CreateToolTip(self.b_install, "Opens the Installer window. Enter a module name to download and install using pip.")
327 self.b_uninstall.grid(column=4, row=3)
328 CreateToolTip(self.b_uninstall, "Completely uninstalls the module selected in the list.")
329 self.b_update.grid(column=4, row=4)
330 CreateToolTip(self.b_update, "Updates the selected module in the list.")
331 self.mainwin.title("Merry")
332 imgicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'icon.png'))
333 self.mainwin.tk.call('wm', 'iconphoto', self.mainwin, imgicon)
334 self.menu = tkinter.Menu(self.mainwin)
335 self.mainwin.config(menu=self.menu)
336
337 self.filemenu = tkinter.Menu(self.mainwin)
338 self.menu.add_cascade(label="Merry", menu=self.filemenu)
339 self.filemenu.add_command(label="About", command=about)
340 self.filemenu.add_command(label="Check Libraries integrity", command=pipcheck)
341 self.filemenu.add_command(label="Show info on selected package", command=pipshow)
342 self.filemenu.add_separator()
343 self.filemenu.add_command(label="Exit", command=self.mainwin.destroy)
344
345 if not self.online:
346 self.b_updatecheck.config(state="disabled")
347 self.b_install.config(state="disabled")
348 self.bicon = tkinter.PhotoImage(file=os.path.join(scriptdir,'reset.png'))
349 self.b_rec = tkinter.Button(self.mainwin, image=self.bicon, command=reconnect)
350 self.b_rec.grid(row=0, column=5)
351 CreateToolTip(self.b_rec, "Reconnect to network.")
352 self.infolab.config(text="No internet connection was found.\nMerry will run in offline mode. (No update checking.)")
353
354 if self.update_check_on_start and self.online:
355 get_updates(self)
356
357 merrygui = pipGuiMan()
358 merrygui.mainwin.mainloop()
359 print(merrygui.pip)